Skip to content

feat(ledger): integrate Ledger v3 operator#490

Open
flemzord wants to merge 31 commits into
mainfrom
feat/ledger-v3-operator-integration
Open

feat(ledger): integrate Ledger v3 operator#490
flemzord wants to merge 31 commits into
mainfrom
feat/ledger-v3-operator-integration

Conversation

@flemzord

@flemzord flemzord commented Jul 15, 2026

Copy link
Copy Markdown
Member

Context

This PR integrates the Ledger v3 Operator into the Formance Operator while preserving the historical Ledger v2 behavior.

The goal is to delegate native provisioning for Ledger versions strictly greater than v3.0.0-alpha, allow Ledger v3 to run alongside Ledger v2 in preparation for migrations, and avoid making the Ledger Operator mandatory for stacks that do not use these features.

What is included

Native Ledger v3 provisioning

  • For Ledger versions strictly greater than v3.0.0-alpha, the Operator creates a Cluster.ledger.formance.com instead of the historical Deployments, Jobs, CronJobs, and Database resources.
  • The replica count continues to use deployments.ledger.replicas. It defaults to 3, and any positive even value is rounded up to the next odd number to preserve quorum (2 -> 3, 4 -> 5).
  • The image, clusterID, debug mode, TLS, reserved labels, and serviceName: ledger remain enforced by the Operator.
  • Resources, image pull secrets, and the service account are overridden only when a corresponding value is provided by Settings or the registry configuration.
  • extraEnv, Auth, and monitoring are merged field by field so that advanced options not managed by Settings are preserved, including Pyroscope, Flight Recorder, scopeMapping, and anonymousScopes.
  • The deployments.ledger.topology-spread-constraints Setting is applied to the v3 Cluster.

Shared configuration through LedgerConfiguration

  • Adds the cluster-scoped LedgerConfiguration.formance.com/v1beta1 CRD.
  • spec.cluster directly reuses the Ledger Operator's ClusterSpec: its descriptions and Kubebuilder validations are expanded into the generated CRD without duplicating the Go type.
  • spec.stacks follows the Settings convention: ['*'] provides the global configuration, while a configuration explicitly targeting a stack takes precedence.
  • The selected configuration is used as the base; values computed from the Stack, Settings, Auth, and registry configuration are then applied according to the precedence rules above.
  • Creating, updating, or deleting a configuration reconciles the affected Ledgers. When no configuration applies, the historical v3 behavior is preserved.

Ledger v3 preview for preparing a v2-to-v3 migration

  • The ledger.v3.preview-version Setting starts an isolated v3 Cluster while fully preserving the Ledger v2 deployment and its Database.
  • /api/ledger/v3 targets the v3 preview, while /api/ledger/v2 and the historical routes continue to target v2.
  • The ledger.BucketService gRPC service is routed to the v3 Cluster.
  • Removing the Setting deletes only the preview resources and routes; Ledger v2 continues to operate.
  • No mirroring or final switch is triggered automatically. Data replication and customer validation remain explicit migration steps.

TLS, Auth, and Gateway exposure

  • Creates the cert-manager Issuer and Certificate resources required for inter-node TLS and Gateway-to-Ledger-v3 traffic.
  • gRPC routes are published only after the Certificate and its Secret are ready.
  • Auth configuration is derived from the stack's Auth module while preserving additional fields defined in LedgerConfiguration.
  • Adds GatewayGRPCAPI and the associated Caddy routing alongside the HTTP rules.

NetworkPolicies

  • Allows Raft and gRPC service traffic between replicas of the same v3 Cluster on ports 7777 and 8888.
  • Uses distinct rules for direct and preview Clusters to preserve the existing selectors.
  • Allows Ledger v3 to reach Ledger v2 only within its own Stack namespace on port 8080, supporting mirror mode without cross-stack access.

Optional dependencies and safeguards

  • On startup, the Operator checks that the Cluster CRD exists and that it has get/list/watch/create/update/patch/delete permissions before registering the Ledger v3 watcher.
  • If the CRD or permissions are unavailable, v2 stacks continue to reconcile normally and v3 previews are ignored without crashing the Operator.
  • A stack configured directly for v3 remains pending when the Ledger Operator is unavailable: it does not silently fall back to v2 resources.
  • A v2-to-v3 transition is blocked while v2 resources exist, and a v3-to-v2 transition is blocked while the direct v3 Cluster exists. The Operator reports a MigrationRequired condition instead of implicitly destroying data.
  • Preview cleanup remains possible when cert-manager is no longer available; inaccessible cert-manager resources are ignored during cleanup.

Expected behavior

  1. A v2 stack without the preview Setting continues to work without the Ledger Operator installed.
  2. A v2 stack with ledger.v3.preview-version and the required CRDs runs v2 and v3 in parallel, with HTTP v2/v3 and gRPC v3 routed correctly.
  3. The same stack with the Ledger v3 CRDs unavailable keeps v2 running and ignores the preview without a global reconciliation failure.
  4. A stack configured with a Ledger v3 version creates a native Cluster and creates neither a Database nor Ledger v2 workloads.
  5. A LedgerConfiguration explicitly targeting a stack always takes precedence over the wildcard configuration.
  6. Operator-reserved values and Settings take precedence over the shared configuration, while unmanaged fields are preserved.
  7. Replica counts are always positive and odd; invalid values are rejected explicitly.
  8. With NetworkPolicies enabled, v3 nodes can form their cluster and can read only the Ledger v2 instance in their own namespace.
  9. Switching Ledger generations never implicitly deletes the other generation's resources.

Validation

  • nix develop --command just pc
    • go mod tidy, linting, and generation for the affected modules
    • CRD, RBAC, Helm, and documentation generation
    • Helm chart linting and rendering
  • nix develop --command just tests
    • 11 Ginkgo suites passed
    • 133 controller specs passed
  • CGO_ENABLED=0 go test ./... with Kubernetes 1.32 envtest assets
  • nix develop --command golangci-lint run --timeout 5m: 0 issues
  • helm lint ./helm/operator --strict
  • helm lint ./helm/crds --strict
  • git diff --check
  • GitHub Actions E2E matrix passed on Kubernetes 1.31, 1.32, 1.33, 1.34, and 1.35

A non-blocking warning remains during just tests: the bundled Ginkgo CLI is 2.27.2, while the Go packages use 2.28.1.

Deployment

The updated CRD chart must be deployed before this Operator version. The Ledger Operator and cert-manager are required to run Ledger v3 Clusters with TLS, but they are not required to continue operating v2 stacks.

gfyrag and others added 19 commits May 28, 2026 17:33
Introduce a new GatewayGRPCAPI custom resource that allows modules to
expose gRPC services through the gateway, following the same pattern as
GatewayHTTPAPI.

gRPC routing uses fully-qualified protobuf service names
(e.g. formance.ledger.v1.LedgerService) instead of HTTP path prefixes.
Traffic is served on the same port (8080) with h2c protocol enabled
conditionally when gRPC APIs are registered.

Each GatewayGRPCAPI creates a dedicated <name>-grpc Kubernetes Service
pointing to the module's gRPC port, avoiding conflicts with existing
HTTP services.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The deletion step was timing out on K8s 1.34 because the script
timeout (2m) was being consumed by kubectl wait --for=delete. Increase
the script timeout to 5m and the wait to 3m for more headroom.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract the operator image build from Earthly into a standard
Dockerfile so it can be used by both Earthly (via FROM DOCKERFILE)
and the new Pulumi deployment app.

The Pulumi app (deployment/operator/) builds the operator image,
applies CRDs, and deploys the operator via its Helm chart.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Helm release creates the namespace itself via createNamespace.
Defaults to formance-system.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Docker build context may not include .git, causing go build to
fail with "error obtaining VCS status". Disable VCS stamping since
version info is already passed via ldflags.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@flemzord, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 33 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0b76d1fa-12a1-4413-9c29-b533ff5e99b8

📥 Commits

Reviewing files that changed from the base of the PR and between f76c581 and 9abe429.

⛔ Files ignored due to path filters (5)
  • config/crd/bases/formance.com_gatewaygrpcapis.yaml is excluded by !**/*.yaml
  • config/crd/bases/formance.com_gatewayhttpapis.yaml is excluded by !**/*.yaml
  • helm/crds/templates/crds/apiextensions.k8s.io_v1_customresourcedefinition_gatewaygrpcapis.formance.com.yaml is excluded by !**/*.yaml
  • helm/crds/templates/crds/apiextensions.k8s.io_v1_customresourcedefinition_gatewayhttpapis.formance.com.yaml is excluded by !**/*.yaml
  • tests/e2e/chainsaw/02-stack-lifecycle/asserts/networkpolicies.yaml is excluded by !**/*.yaml
📒 Files selected for processing (4)
  • api/formance.com/v1beta1/gatewaybackend_types.go
  • api/formance.com/v1beta1/gatewayhttpapi_types.go
  • docs/09-Configuration reference/02-Custom Resource Definitions.md
  • internal/tests/gatewayhttpapi_controller_test.go

Walkthrough

The change adds Ledger v3 reconciliation and configuration, gRPC gateway APIs and routing, backend TLS support, multi-stage container packaging, Pulumi deployment automation, network policies, documentation, and extensive unit and integration coverage.

Changes

Ledger v3 and gateway integration

Layer / File(s) Summary
Container and Pulumi deployment
.gitignore, Dockerfile, Earthfile, deployment/operator/*
Adds multi-stage image builds, build metadata and license injection, Pulumi image publishing, CRD application, Helm deployment, and configuration helpers.
API contracts and shared configuration
PROJECT, api/formance.com/v1beta1/*, internal/resources/auths/env.go, internal/resources/settings/opentelemetry.go
Adds GatewayGRPCAPI, backend references, LedgerConfiguration, optional gRPC status synchronization, protected auth configuration, and OpenTelemetry logs support.
gRPC resource lifecycle
internal/resources/gatewaygrpcapis/*, internal/resources/gatewayhttpapis/create.go
Creates and reconciles gRPC services, supports backend references, and adds HTTP rules targeting explicit backends.
Gateway routing and backend TLS
internal/resources/gateways/*
Renders HTTP and gRPC routes, supports TLS upstreams, mounts referenced secrets, hashes secret contents, and watches gRPC APIs and backend secrets.
Ledger v3 reconciliation and TLS
internal/resources/ledgers/*, internal/resources/stacks/networkpolicies.go
Adds v3 capability detection, migration guards, Cluster composition, cert-manager TLS resources, preview routing, readiness conditions, cleanup, configuration inheritance, and Ledger v3 traffic policies.
Documentation and validation
docs/*, internal/tests/*, internal/resources/ledgers/*_test.go, internal/resources/gateways/caddyfile_test.go
Documents Ledger v3, gateway CRDs, and network isolation while adding unit, controller, gateway, Ledger v3, network-policy, and regression coverage.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

  • formancehq/operator#423: The Dockerfile and Earthfile license-key build arguments support injecting the public key used by offline license validation.

Suggested reviewers: sylr, gfyrag

Poem

I’m a rabbit with clusters to tend,
Routing gRPC round every bend.
TLS secrets tucked tight,
V3 hops into sight—
And builds bloom where clean containers blend.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: integrating Ledger v3 operator support.
Description check ✅ Passed The description is directly related to the Ledger v3 integration and matches the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/ledger-v3-operator-integration

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@flemzord
flemzord requested review from gfyrag and sylr July 15, 2026 13:50
@flemzord
flemzord marked this pull request as ready for review July 15, 2026 13:50
@flemzord
flemzord requested a review from a team as a code owner July 15, 2026 13:50
@NumaryBot

NumaryBot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

🛑 Changes requested — automated review

All previously raised issues (security, deployment, networking, documentation, and logic correctness) appear to have been resolved and marked as such in the prior discussion. One new finding remains from this review round: the GatewayGRPCAPI reconciler lacks the admission guard that GatewayHTTPAPI already has for backendRef name collisions. When a GatewayGRPCAPI's spec.backendRef.name matches the conventional managed Service name (<spec.name>-grpc), the reconciler deletes that Service and the Caddy route then points at a nonexistent backend, silently breaking gRPC routing on every reconcile. Adding the same kind of collision check already present for the HTTP API would fix this.

@NumaryBot NumaryBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NumaryBot posted 2 new inline findings.

Summary: #490 (comment)

Comment thread internal/resources/ledgers/v3_preview.go Outdated
Comment thread internal/resources/ledgers/v3.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 16

🧹 Nitpick comments (1)
internal/tests/networkpolicy_controller_test.go (1)

100-107: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add slice length assertions for consistency and safety.

While Gomega safely catches panics within Eventually blocks, it is better practice to explicitly assert the lengths of Ingress and From before indexing them. This provides clearer error messages upon failure and matches the pattern used in the allow-ledger-v3-cluster check above.

♻️ Proposed refactor
 				// Existing preview clusters keep their historical immutable selector.
 				Eventually(func(g Gomega) {
 					np := &networkingv1.NetworkPolicy{}
 					g.Expect(LoadResource(stack.Name, "allow-ledger-v3-preview-cluster", np)).To(Succeed())
 					g.Expect(np).To(BeControlledBy(stack))
 					g.Expect(np.Spec.PodSelector.MatchLabels).To(HaveKeyWithValue("formance.com/ledger-v3-preview", "true"))
+					g.Expect(np.Spec.Ingress).To(HaveLen(1))
+					g.Expect(np.Spec.Ingress[0].From).To(HaveLen(1))
 					g.Expect(np.Spec.Ingress[0].From[0].PodSelector.MatchLabels).To(Equal(np.Spec.PodSelector.MatchLabels))
 				}).Should(Succeed())
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/tests/networkpolicy_controller_test.go` around lines 100 - 107, Add
explicit length assertions in the existing-cluster check within the Eventually
block before indexing np.Spec.Ingress and np.Spec.Ingress[0].From. Assert each
collection has the expected element count, then retain the existing selector
equality assertion.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@deployment/operator/.gitignore`:
- Line 1: Update the ignore pattern in the deployment/operator .gitignore so it
targets the bin directory relative to that file, ensuring
deployment/operator/bin/ is ignored.

In `@deployment/operator/helpers.go`:
- Around line 94-106: Update the platform selection logic around cfg.Get("arch")
so an unset architecture leaves all entries in allPlatforms unchanged,
preserving both supported platforms. Apply the HasSuffix filtering and
linux-architecture fallback only when arch is explicitly configured, rather than
defaulting it to amd64.
- Around line 159-164: Update the CacheToRegistryArgs construction in the
CacheTo configuration so Ref contains only the registry image name and tag,
removing the ",mode=max" suffix. Set the typed Mode field on CacheToRegistryArgs
to "max" instead.

In `@deployment/operator/main.go`:
- Around line 36-42: Validate the result of filepath.Glob in the CRD loading
flow before declaring crds, and return a descriptive error when no manifest
paths are discovered. Preserve the existing glob-error handling and continue
processing the discovered files normally.
- Around line 64-76: Update the licence configuration in the main Pulumi setup
to retrieve licence-token with config.GetSecret(ctx, "licence-token") instead of
cfg.Get, and pass that secret output through unchanged when assigning the
"token" Helm value so it remains secret in the Pulumi graph and state. Keep the
existing issuer fallback and other licenceValues entries unchanged.

In `@Dockerfile`:
- Around line 28-34: Update the final Docker image stage after copying the
operator binary to create an unprivileged user and configure the image to run as
that user, ensuring the existing ENTRYPOINT continues to invoke
/usr/bin/operator without root privileges.

In `@docs/04-Modules/03-Ledger.md`:
- Around line 7-8: Update the PostgreSQL and Broker Markdown links in the ledger
documentation to use descriptive link text such as “PostgreSQL configuration”
and “message broker configuration” instead of “here,” while preserving their
existing destinations.
- Around line 10-12: Update the Ledger v3 requirements documentation to
explicitly list cert-manager as a required dependency, including its Issuer and
Certificate CRDs. Keep the existing Ledger Operator and native-storage
requirements, and ensure the guidance applies consistently to all referenced v3
deployment sections.

In `@docs/09-Configuration` reference/02-Custom Resource Definitions.md:
- Line 2559: Fix the `ClusterSpec` reference in the Cluster table by either
generating the missing `ClusterSpec` schema heading and fragment target or
removing the link while retaining the type text. Ensure the rendered
documentation no longer points to the nonexistent `#clusterspec` anchor.
- Around line 2385-2387: Remove the duplicate, undescribed ready status rows
from both Gateway status tables in the documentation source, retaining the
documented ready entry with its description. Regenerate the generated reference
file so both affected sections contain only one ready row.

In `@Earthfile`:
- Around line 48-54: Declare the `tag` build argument before the `FROM
DOCKERFILE` statement that passes `$tag` as the `VERSION` build argument. Keep
the existing `LICENCE_PUBLIC_KEY_B64` and `EARTHLY_BUILD_SHA` declarations and
forwarding unchanged.

In `@internal/resources/gateways/Caddyfile.gotpl`:
- Around line 152-155: Update the health-check probe URLs in the Caddyfile
template branches around HealthCheckBackend so they use https:// when
HealthCheckBackend.TLS is configured and http:// otherwise. Apply the backend’s
configured CA trust settings to these probes, preserving the existing host,
port, and endpoint paths for both default and custom health checks.

In `@internal/resources/ledgers/init.go`:
- Around line 51-65: Update the Ledger downgrade logic around
ledgerV3ClusterAvailable and getV3Cluster to distinguish confirmed v3 CRD
absence from discovery, list, or watch failures. When Cluster access is unknown,
preserve the migration guard and stop v2 reconciliation instead of treating the
cluster as absent; only proceed with the current v2 path after confirmed absence
or successful existence validation.

In `@internal/resources/ledgers/v3_preview.go`:
- Around line 143-159: Update the preview cleanup flow around the cert-manager
availability guard to find and delete the core TLS Secret independently, before
returning when ledgerV3CertManagerAvailable is false. Ensure deletion is
restricted by the Secret’s preview label or equivalent ownership check, preserve
not-found handling, and avoid making Certificate cleanup a prerequisite; adjust
TestDeleteLedgerV3PreviewSkipsCertManagerResourcesWhenUnavailable to assert the
Secret is removed while cert-manager resources remain untouched.

In `@internal/resources/ledgers/v3_spec.go`:
- Around line 142-143: Update the configuration mapping for ReadKeySetMaxRetries
in the spec construction to validate the int value against math.MaxInt32 before
converting it to int32; only assign spec.Auth.ReadKeySetMaxRetries when it is
nonzero and within range, preventing overflow into a negative retry count.

In `@internal/tests/gateway_controller_test.go`:
- Around line 418-421: Update the Eventually assertion around LoadResource to
require that
deployment.Spec.Template.Annotations["formance.com/backend-tls-secrets-hash"] is
both different from initialHash and non-empty, preserving the existing rollout
wait while rejecting a removed or missing annotation.

---

Nitpick comments:
In `@internal/tests/networkpolicy_controller_test.go`:
- Around line 100-107: Add explicit length assertions in the existing-cluster
check within the Eventually block before indexing np.Spec.Ingress and
np.Spec.Ingress[0].From. Assert each collection has the expected element count,
then retain the existing selector equality assertion.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 51707418-0707-455a-91e5-004d025d7d96

📥 Commits

Reviewing files that changed from the base of the PR and between f3cfcb1 and cfbc18e.

⛔ Files ignored due to path filters (41)
  • config/crd/bases/formance.com_gatewaygrpcapis.yaml is excluded by !**/*.yaml
  • config/crd/bases/formance.com_gatewayhttpapis.yaml is excluded by !**/*.yaml
  • config/crd/bases/formance.com_gateways.yaml is excluded by !**/*.yaml
  • config/crd/bases/formance.com_ledgerconfigurations.yaml is excluded by !**/*.yaml
  • config/crd/kustomization.yaml is excluded by !**/*.yaml
  • config/rbac/ledgerconfiguration_editor_role.yaml is excluded by !**/*.yaml
  • config/rbac/ledgerconfiguration_viewer_role.yaml is excluded by !**/*.yaml
  • config/rbac/role.yaml is excluded by !**/*.yaml
  • config/samples/formance.com_v1beta1_ledgerconfiguration.yaml is excluded by !**/*.yaml
  • config/samples/kustomization.yaml is excluded by !**/*.yaml
  • deployment/operator/Pulumi.yaml is excluded by !**/*.yaml
  • deployment/operator/go.mod is excluded by !**/*.mod
  • deployment/operator/go.sum is excluded by !**/*.sum, !**/*.sum
  • docs/09-Configuration reference/settings.catalog.json is excluded by !**/*.json
  • go.mod is excluded by !**/*.mod
  • go.sum is excluded by !**/*.sum, !**/*.sum
  • helm/crds/templates/crds/apiextensions.k8s.io_v1_customresourcedefinition_gatewaygrpcapis.formance.com.yaml is excluded by !**/*.yaml
  • helm/crds/templates/crds/apiextensions.k8s.io_v1_customresourcedefinition_gatewayhttpapis.formance.com.yaml is excluded by !**/*.yaml
  • helm/crds/templates/crds/apiextensions.k8s.io_v1_customresourcedefinition_gateways.formance.com.yaml is excluded by !**/*.yaml
  • helm/crds/templates/crds/apiextensions.k8s.io_v1_customresourcedefinition_ledgerconfigurations.formance.com.yaml is excluded by !**/*.yaml
  • helm/operator/templates/gen/rbac.authorization.k8s.io_v1_clusterrole_formance-manager-role.yaml is excluded by !**/gen/**, !**/*.yaml, !**/gen/**
  • internal/tests/crds/cert-manager.io_certificates.yaml is excluded by !**/*.yaml
  • internal/tests/crds/cert-manager.io_issuers.yaml is excluded by !**/*.yaml
  • internal/tests/crds/ledger.formance.com_clusters.yaml is excluded by !**/*.yaml
  • internal/tests/testdata/resources/gateway-controller/configmap-with-audit.yaml is excluded by !**/*.yaml
  • internal/tests/testdata/resources/gateway-controller/configmap-with-ledger-and-another-service.yaml is excluded by !**/*.yaml
  • internal/tests/testdata/resources/gateway-controller/configmap-with-ledger-and-grpc.yaml is excluded by !**/*.yaml
  • internal/tests/testdata/resources/gateway-controller/configmap-with-ledger-only.yaml is excluded by !**/*.yaml
  • internal/tests/testdata/resources/gateway-controller/configmap-with-opentelemetry.yaml is excluded by !**/*.yaml
  • tests/e2e/chainsaw/02-stack-lifecycle/asserts/networkpolicies.yaml is excluded by !**/*.yaml
  • tests/e2e/chainsaw/14-ledger-module/chainsaw-test.yaml is excluded by !**/*.yaml
  • tests/e2e/chainsaw/14-ledger-module/resources/database.yaml is excluded by !**/*.yaml
  • tests/e2e/chainsaw/14-ledger-module/resources/stack.yaml is excluded by !**/*.yaml
  • tests/e2e/chainsaw/26-gatewaygrpcapi-sync/chainsaw-test.yaml is excluded by !**/*.yaml
  • tests/e2e/chainsaw/26-gatewaygrpcapi-sync/resources/gateway.yaml is excluded by !**/*.yaml
  • tests/e2e/chainsaw/26-gatewaygrpcapi-sync/resources/grpcapi-updated.yaml is excluded by !**/*.yaml
  • tests/e2e/chainsaw/26-gatewaygrpcapi-sync/resources/grpcapi.yaml is excluded by !**/*.yaml
  • tests/e2e/chainsaw/26-gatewaygrpcapi-sync/resources/httpapi-ledger.yaml is excluded by !**/*.yaml
  • tests/e2e/chainsaw/26-gatewaygrpcapi-sync/resources/stack.yaml is excluded by !**/*.yaml
  • tools/kubectl-stacks/go.mod is excluded by !**/*.mod
  • tools/kubectl-stacks/go.sum is excluded by !**/*.sum, !**/*.sum
📒 Files selected for processing (50)
  • .gitignore
  • Dockerfile
  • Earthfile
  • PROJECT
  • api/formance.com/v1beta1/gateway_types.go
  • api/formance.com/v1beta1/gatewaybackend_types.go
  • api/formance.com/v1beta1/gatewaygrpcapi_types.go
  • api/formance.com/v1beta1/gatewayhttpapi_types.go
  • api/formance.com/v1beta1/ledger_types.go
  • api/formance.com/v1beta1/ledgerconfiguration_types.go
  • api/formance.com/v1beta1/zz_generated.deepcopy.go
  • deployment/operator/.gitignore
  • deployment/operator/helpers.go
  • deployment/operator/main.go
  • docs/04-Modules/03-Ledger.md
  • docs/09-Configuration reference/01-Settings.md
  • docs/09-Configuration reference/02-Custom Resource Definitions.md
  • internal/core/setup.go
  • internal/resources/all.go
  • internal/resources/auths/env.go
  • internal/resources/gatewaygrpcapis/create.go
  • internal/resources/gatewaygrpcapis/init.go
  • internal/resources/gatewayhttpapis/create.go
  • internal/resources/gateways/Caddyfile.gotpl
  • internal/resources/gateways/caddyfile.go
  • internal/resources/gateways/caddyfile_test.go
  • internal/resources/gateways/configuration.go
  • internal/resources/gateways/deployment.go
  • internal/resources/gateways/init.go
  • internal/resources/ledgers/init.go
  • internal/resources/ledgers/v3.go
  • internal/resources/ledgers/v3_preview.go
  • internal/resources/ledgers/v3_spec.go
  • internal/resources/ledgers/v3_spec_test.go
  • internal/resources/ledgers/v3_test.go
  • internal/resources/ledgers/v3_tls.go
  • internal/resources/settings/opentelemetry.go
  • internal/resources/stacks/networkpolicies.go
  • internal/tests/application_test.go
  • internal/tests/auth_scopes_settings_test.go
  • internal/tests/gateway_controller_test.go
  • internal/tests/gatewaygrpcapi_controller_test.go
  • internal/tests/jobs_controller_test.go
  • internal/tests/ledger_controller_test.go
  • internal/tests/ledger_v3_controller_test.go
  • internal/tests/networkpolicy_controller_test.go
  • internal/tests/orchestration_controller_test.go
  • internal/tests/registries_test.go
  • internal/tests/transactionplane_controller_test.go
  • internal/tests/wallets_controller_test.go

Comment thread deployment/operator/.gitignore Outdated
Comment thread deployment/operator/helpers.go
Comment thread deployment/operator/helpers.go
Comment thread deployment/operator/main.go
Comment thread deployment/operator/main.go Outdated
Comment thread internal/resources/gateways/Caddyfile.gotpl
Comment thread internal/resources/ledgers/init.go
Comment thread internal/resources/ledgers/v3_preview.go Outdated
Comment thread internal/resources/ledgers/v3_spec.go
Comment thread internal/tests/gateway_controller_test.go Outdated
`ledgerV3TLSName` returned `<stack>-tls`, the same Secret name the gateway
ingress already uses for its own TLS certificate
(`internal/resources/gateways/ingress.go` names it `<gateway>-tls`, and the
gateway is named after the stack).

When gateway ingress TLS is enabled, this duplicates an existing mechanism:
two independent cert-manager clients target the *same* Secret —

  - the ledger v3 `Certificate` (self-signed, per-stack Issuer), and
  - the gateway ingress (ingress-shim, public ClusterIssuer)

Each observes the other's issuance as `IncorrectIssuer` and reissues,
producing an unbounded reissue race: the keypair rotates every few seconds,
the ledger raft mTLS cert reloader repeatedly fails with
`tls: private key does not match public key`, and the raft cluster never
forms quorum (stuck Degraded).

Rename the raft TLS Secret to `<stack>-ledger-v3-tls` so it no longer
collides with the gateway ingress Secret. The gateway consumes this backend
certificate by the `GatewayBackendTLSSecretLabel` label (not by name), so a
dedicated name is safe. `ledgerV3TLSName` is the single source used by the
Certificate/secretName, the ledger pod mount, the gateway backend ref and
the preview cleanup, so the rename propagates consistently. This also matches
the name already expected in `v3_spec_test.go` (`stack0-ledger-v3-tls`).

Co-authored-by: David Ragot <david@formance.com>

@NumaryBot NumaryBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NumaryBot posted 13 new inline findings.

Summary: #490 (comment)

Comment thread internal/resources/stacks/networkpolicies.go Outdated
Comment thread deployment/operator/helpers.go
Comment thread deployment/operator/helpers.go
Comment thread deployment/operator/main.go
Comment thread deployment/operator/main.go Outdated
Comment thread internal/resources/ledgers/v3_preview.go Outdated
Comment thread docs/04-Modules/03-Ledger.md Outdated
Comment thread internal/resources/ledgers/v3_spec.go
Comment thread deployment/operator/.gitignore Outdated
Comment thread docs/04-Modules/03-Ledger.md Outdated

@NumaryBot NumaryBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NumaryBot posted 15 new inline findings.

Summary: #490 (comment)

Comment thread internal/resources/ledgers/v3_preview.go Outdated
Comment thread internal/resources/ledgers/v3.go
Comment thread internal/resources/ledgers/v3_preview.go Outdated
Comment thread internal/resources/ledgers/init.go
Comment thread internal/resources/ledgers/v3.go
Comment thread internal/resources/gateways/Caddyfile.gotpl
Comment thread docs/04-Modules/03-Ledger.md Outdated
Comment thread internal/resources/ledgers/v3_spec.go
Comment thread deployment/operator/.gitignore Outdated
Comment thread docs/04-Modules/03-Ledger.md Outdated

@NumaryBot NumaryBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NumaryBot posted 14 new inline findings.

Summary: #490 (comment)

Comment thread internal/resources/ledgers/v3.go
Comment thread internal/resources/ledgers/init.go
Comment thread internal/resources/ledgers/v3_preview.go
Comment thread internal/resources/gateways/Caddyfile.gotpl
Comment thread internal/resources/stacks/networkpolicies.go
Comment thread deployment/operator/helpers.go
Comment thread Earthfile
Comment thread docs/04-Modules/03-Ledger.md
Comment thread internal/resources/ledgers/v3_spec.go
Comment thread internal/tests/gateway_controller_test.go Outdated

@NumaryBot NumaryBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NumaryBot posted 16 new inline findings.

Summary: #490 (comment)

Comment thread internal/resources/ledgers/v3.go
Comment thread internal/resources/ledgers/v3_preview.go
Comment thread internal/resources/gateways/Caddyfile.gotpl
Comment thread internal/resources/ledgers/v3.go
Comment thread internal/resources/stacks/networkpolicies.go
Comment thread docs/04-Modules/03-Ledger.md
Comment thread api/formance.com/v1beta1/ledgerconfiguration_types.go
Comment thread internal/resources/ledgers/v3_spec.go
Comment thread deployment/operator/.gitignore
Comment thread docs/04-Modules/03-Ledger.md

@NumaryBot NumaryBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NumaryBot posted 1 new inline finding.

Summary: #490 (comment)

Comment thread internal/resources/gateways/init.go

@NumaryBot NumaryBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NumaryBot posted 2 new inline findings.

Summary: #490 (comment)

Comment thread internal/resources/ledgers/v3.go
Comment thread internal/resources/ledgers/v3_tls.go

@NumaryBot NumaryBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NumaryBot review complete: no remaining inline findings.

Resolved 2 stale NumaryBot review threads (2 fixed, 0 outdated).

Summary: #490 (comment)

@NumaryBot NumaryBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NumaryBot posted 2 new inline findings.

Summary: #490 (comment)

Comment thread internal/resources/ledgers/v3.go
Comment thread internal/resources/ledgers/v3_preview.go

@NumaryBot NumaryBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NumaryBot posted 1 new inline finding.

Summary: #490 (comment)

Comment thread internal/resources/gatewaygrpcapis/init.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/resources/ledgers/v3_spec.go`:
- Around line 147-150: Update the ReadKeySetMaxRetries validation in the
configuration conversion flow to reject all negative values while retaining the
existing int32 upper-bound check. Allow zero and positive values through
unchanged, and preserve the current error-return behavior for invalid
configuration.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f2de6f2a-68cc-4bbc-ad37-ab7611b0ef1a

📥 Commits

Reviewing files that changed from the base of the PR and between e605fad and f76c581.

⛔ Files ignored due to path filters (9)
  • config/crd/bases/formance.com_gatewaygrpcapis.yaml is excluded by !**/*.yaml
  • config/crd/bases/formance.com_gatewayhttpapis.yaml is excluded by !**/*.yaml
  • config/crd/bases/formance.com_ledgerconfigurations.yaml is excluded by !**/*.yaml
  • deployment/operator/Pulumi.yaml is excluded by !**/*.yaml
  • docs.config.yaml is excluded by !**/*.yaml
  • docs/09-Configuration reference/settings.catalog.json is excluded by !**/*.json
  • helm/crds/templates/crds/apiextensions.k8s.io_v1_customresourcedefinition_gatewaygrpcapis.formance.com.yaml is excluded by !**/*.yaml
  • helm/crds/templates/crds/apiextensions.k8s.io_v1_customresourcedefinition_gatewayhttpapis.formance.com.yaml is excluded by !**/*.yaml
  • helm/crds/templates/crds/apiextensions.k8s.io_v1_customresourcedefinition_ledgerconfigurations.formance.com.yaml is excluded by !**/*.yaml
📒 Files selected for processing (22)
  • Dockerfile
  • api/formance.com/v1beta1/gatewaybackend_types.go
  • api/formance.com/v1beta1/gatewaygrpcapi_types.go
  • api/formance.com/v1beta1/gatewayhttpapi_types.go
  • api/formance.com/v1beta1/ledgerconfiguration_types.go
  • deployment/operator/.gitignore
  • deployment/operator/helpers.go
  • deployment/operator/main.go
  • docs/04-Modules/03-Ledger.md
  • docs/09-Configuration reference/02-Custom Resource Definitions.md
  • internal/resources/gateways/Caddyfile.gotpl
  • internal/resources/ledgers/init.go
  • internal/resources/ledgers/v3.go
  • internal/resources/ledgers/v3_preview.go
  • internal/resources/ledgers/v3_spec.go
  • internal/resources/ledgers/v3_spec_test.go
  • internal/resources/ledgers/v3_test.go
  • internal/resources/ledgers/v3_tls.go
  • internal/resources/stacks/networkpolicies.go
  • internal/tests/gateway_controller_test.go
  • internal/tests/ledger_v3_controller_test.go
  • internal/tests/networkpolicy_controller_test.go
💤 Files with no reviewable changes (2)
  • api/formance.com/v1beta1/gatewayhttpapi_types.go
  • api/formance.com/v1beta1/gatewaygrpcapi_types.go
🚧 Files skipped from review as they are similar to previous changes (15)
  • deployment/operator/.gitignore
  • Dockerfile
  • internal/resources/ledgers/v3_spec_test.go
  • docs/04-Modules/03-Ledger.md
  • api/formance.com/v1beta1/ledgerconfiguration_types.go
  • api/formance.com/v1beta1/gatewaybackend_types.go
  • deployment/operator/main.go
  • deployment/operator/helpers.go
  • internal/tests/networkpolicy_controller_test.go
  • internal/resources/ledgers/v3_preview.go
  • internal/tests/gateway_controller_test.go
  • internal/resources/gateways/Caddyfile.gotpl
  • docs/09-Configuration reference/02-Custom Resource Definitions.md
  • internal/resources/ledgers/v3.go
  • internal/tests/ledger_v3_controller_test.go

Comment on lines +147 to +150
if configuration.ReadKeySetMaxRetries > math.MaxInt32 || configuration.ReadKeySetMaxRetries < math.MinInt32 {
return fmt.Errorf("auth read key set max retries must fit in int32, got %d", configuration.ReadKeySetMaxRetries)
}
spec.Auth.ReadKeySetMaxRetries = pointerTo(int32(configuration.ReadKeySetMaxRetries))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail
rg -n -C4 'ReadKeySetMaxRetries|read-key-set-max-retries' .

Repository: formancehq/operator

Length of output: 9273


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

rg -n -C3 'AUTH_READ_KEY_SET_MAX_RETRIES|ReadKeySetMaxRetries|readKeySetMaxRetries' internal api config helm

Repository: formancehq/operator

Length of output: 20514


Reject negative retry counts here. ReadKeySetMaxRetries is a retry count, and 0 already means “omit the env var / keep the default.” Allow values in the non-negative range only; < 0 forwards invalid config to the ledger.

🧰 Tools
🪛 ast-grep (0.44.1)

[warning] 149-149: Narrowing a non-constant integer to a smaller fixed-width type (int8/int16/int32, uint8/uint16/uint32) can silently overflow or wrap, yielding negative or truncated values that are dangerous in size, length, or index logic. Validate the source value is within the target type's range before converting (e.g. bounds-check, or use a checked helper), and avoid narrowing untrusted or len()/parsed values.
Context: int32(configuration.ReadKeySetMaxRetries)
Note: [CWE-190] Integer Overflow or Wraparound.

(integer-overflow-narrowing-conversion-go)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/resources/ledgers/v3_spec.go` around lines 147 - 150, Update the
ReadKeySetMaxRetries validation in the configuration conversion flow to reject
all negative values while retaining the existing int32 upper-bound check. Allow
zero and positive values through unchanged, and preserve the current
error-return behavior for invalid configuration.

@NumaryBot NumaryBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NumaryBot posted 1 new inline finding.

Summary: #490 (comment)

Comment thread api/formance.com/v1beta1/gatewayhttpapi_types.go

@NumaryBot NumaryBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NumaryBot posted 1 new inline finding.

Summary: #490 (comment)

//+kubebuilder:rbac:groups=formance.com,resources=gatewaygrpcapis/finalizers,verbs=update

func Reconcile(ctx Context, _ *v1beta1.Stack, grpcAPI *v1beta1.GatewayGRPCAPI) error {
if grpcAPI.Spec.BackendRef != nil {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 [major] Missing admission guard for backendRef name collision with managed gRPC Service

When a GatewayGRPCAPI sets spec.backendRef.name to the conventional managed Service name (<spec.name>-grpc), the reconciler deletes that same Service and the generated Caddy route then points at a missing backend. The HTTP API added an equivalent admission guard for this collision, but the new gRPC API has no analogous validation. Users reusing the conventional Service name for a backendRef override can silently break gRPC routing on every reconcile cycle.

Suggestion: Add an admission validation (webhook or reconciler-level rejection) that rejects any GatewayGRPCAPI whose spec.backendRef.name matches the conventional managed Service name (<spec.name>-grpc), mirroring the guard already in place for GatewayHTTPAPI.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of scope per maintainer direction, consistently with the earlier public GatewayGRPCAPI finding. Ledger v3 uses ledger- as its backendRef and never reuses the historical -grpc Service, so this integration is unaffected.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants